
/*
  Temperature Regulator 
  Measures with a DS18B20 the temperature (of a Crockpot) 
  and switches it on an off accordingly.
  The temperature is shown on a LCD display
  Hannes Hassler 2013
  GNU GENERAL PUBLIC LICENSE (GPL3)
  http://www.gnu.org/licenses/gpl-3.0.html 
 */
 
#include "Wire.h"
#include <DS1107H.h>
#include <LiquidCrystal.h>
#include <EEPROM.h>
#include <OneWire.h>
#include <DallasTemperature.h>

/*
Connection of Dallas-TemperaturSensor (3-wires, red, white, black)
red->5V
black->GND
white->Pin fuer digitales Thermometer(ONE_WIRE_BUS)
Between red(5V) and white (Signal) there must be additionally an around 5KOhm pullup resistor (I took 4.7KOhm)
*/


//An analogous Pin
#define TEMPERATURE_PIN 0

#define COUNTER_LIMIT 5000
#define DISPLAY_COUNTER_LIMIT 1000

#define DISPLAY_ANZ 2
#define DISPLAY_TEMP_IND 0
#define DISPLAY_TIME_IND 1
#define DISPLAY_CALIB_IND 2

//PIN fuer digitales Thermometer
#define ONE_WIRE_BUS 8

//Digital Pin fuer Taster
#define UP_PIN          13
#define DOWN_PIN        12
#define MENU_SWITCH_PIN 11

//EEPROM CalibrateTabSize
#define calibrateTabSize 20
#define calibrateTabOffset 1
#define stableTempTabSize 20
#define stableTempTabOffset 22

#define lastTemperatureTabSize 24
#define consideredStableDiff   0.2

//Digital Pin to put the SSR ON and OFF
#define SWITCH_PIN 6
#define LCD_RS_PIN   10
#define LCD_ENBL_PIN 7
#define LCD_D4_PIN   5
#define LCD_D5_PIN   4
#define LCD_D6_PIN   3
#define LCD_D7_PIN   2
#define LCD_COLUMNS  16
#define LCD_ROWS     2

#define stablePSArrSize 10

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature digitalTempSensor(&oneWire);

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(LCD_RS_PIN, LCD_ENBL_PIN, LCD_D4_PIN, LCD_D5_PIN, LCD_D6_PIN, LCD_D7_PIN);

int     tempPinVal;
float   tempPinAverage;
float   tempPinMax=0;
float   tempPinMin=1023;
float   calcResist;
float   calcTemp;

char ch;

long numberOfSamples=1000;
int measurementRounds=1;

int temperatureSample=100;

boolean switchOn;
boolean scheduleOn;

int onPercentage=75;
float onPercentageAvg=-1.0;
int calibOnPercentage=-1;
float zielTemperatur=20;
double zielPerc;
long mIter=0;

DS1107H clock;
unsigned long clockAbsoluteSecs;
unsigned long lastTempTakenAbsSecs=0;
int secsBetweenMeasurements=15;

int displayIndicator=0;

int menuPinVal;
int upPinVal;
unsigned long pressedButtonTime=0;
int downPinVal;

                   
byte calibrateTabBt[calibrateTabSize];
boolean newCalibrateValue=false;
boolean showTempRegularly=true;

int measureIndex=0;
int measureCounter=0;
int measureCounterLimit=50;
float lastTemperatures[lastTemperatureTabSize];
float DlastTemperatures[lastTemperatureTabSize];
float DDlastTemperatures[lastTemperatureTabSize];

float stablePSArr[stablePSArrSize];
float psCorrFactor=1.0;

String version="0.95";

byte lowbyte;
byte highbyte;
int  intFrom2Bytes;

float v14;
float testResult;

int measuredTemp10;
float measuredTemp;
float bestFactor;

int relevantBackMinutes=5;
int lastCorrection=0;
boolean stillCorrectionPossible=true;

void setup() {   
  
  pinMode(UP_PIN,INPUT);
  pinMode(DOWN_PIN,INPUT);
  pinMode(MENU_SWITCH_PIN,INPUT);
  
  pinMode(SWITCH_PIN, OUTPUT);  
  putSwitch(false);
  
  initStablePSArr();
  
  lcd.begin(LCD_COLUMNS, LCD_ROWS);
  lcd.print("Initializing...");
  
  Serial.begin(9600);  
  //to set the timer chip  
  /*
  byte second=0;
  byte minute=35;
  byte hour=15;
  byte dayOfWeek=5;
  byte dayOfMonth=01;
  byte month=11;
  byte year=13;
  clock.setDate(second,minute,hour,dayOfWeek,dayOfMonth,month,year);    
  */
  
  double testInput=2.0;
  double testResult;
  
  digitalTempSensor.begin();
  
}

void loop() {
  
  static int v = 0; 
  static float retV=0.0;
  
  
  if (zielTemperatur==-10.0 && calibOnPercentage!=10.0) {
   calibOnPercentage=10.0;
   showln("calibOnPercentage=",10);
   showTempRegularly=!showTempRegularly;   
  }

 if (Serial.available()) { 
      ch = Serial.read();  
      switch(ch) {
       case '0'...'9':
          v = v * 10 + ch - '0';
          break;
        case 'e':
          showln("test val:",v);                      
          v = 0; 
          break; 
        case 'f':
          showln("test stablePSvArr versus stablePS",0);        
          retV=stablePSvArr(v);
          showln("stablePSvArr(v)",retV);
          retV=stablePsx(v);
          showln("stablePs(v)",retV);
          v=0;  
          break;
        case 'F':
          showln("stablePSArr",0);
          for (int i=0; i<stablePSArrSize; i++) {
            showln("i",stablePSArr[i]);
          }
          break;
        case 'i':
          showln("interval:",v);
          secsBetweenMeasurements=v;
          v=0;
          break;        
        case 'l':
          for (int mIdx=0; mIdx<lastTemperatureTabSize;mIdx++) {
             show("lastTemp",mIdx);
             showln("=",lastTemperatures[mIdx]);
          }     
        case 'm':
          show("lastTemp=:",lastTemperatures[modIdx(measureIndex-1,lastTemperatureTabSize)]); 
          show(" DlastTemp=:",DlastTemperatures[modIdx(measureIndex-1,lastTemperatureTabSize)]);
          showln(" DDlastTemp=:",DDlastTemperatures[modIdx(measureIndex-1,lastTemperatureTabSize)]);
          break;        
        case 'n':
          showln("number of Samples:",numberOfSamples);                                
          break;
        case 'N':
          numberOfSamples=v;
          showln("new number of Samples:",numberOfSamples); 
          v=0;
          break;
        case 'o':
          calibOnPercentage=v;
          showln("calibOnPercentage=",v); 
          v=0;
          break;
        case 'O':
          calibOnPercentage=-1;
          showln("calibOnPercentage=",-1); 
          v=0;
          break;
       case 'p':
          onPercentage=v;
          showln("new onPercentage:",onPercentage); 
          v=0;
          break;
       case 'r':
          showTempRegularly=!showTempRegularly;
          showln("showTempRegularly:",showTempRegularly);
          break;
       case 's':
           zielTemperatur=v;
           showln("Zieltemperatur=",zielTemperatur);
           zielPerc=stablePSvArr(zielTemperatur);
           showln("P_s=",zielPerc);
           v=0;
           break;              
    }
  }  
    
  
  handleButtonTrigger();   
  putSwitch(isOn(onPercentage)&&scheduleOn);
  
  clockGetDateTime();   
  if (clockAbsoluteSecs-lastTempTakenAbsSecs>=secsBetweenMeasurements) {     
    
     
     digitalTempSensor.requestTemperatures(); // Send the command to get temperatures  
     //Serial.print(digitalTempSensor.getTempCByIndex(0)); // Why "byIndex"? 
     calcTemp=digitalTempSensor.getTempCByIndex(0);
    // You can have more than one IC on the same bus. 
    // 0 refers to the first IC on the wire   
          
     lastTemperatures[measureIndex]=calcTemp;     
     DlastTemperatures[measureIndex]=
                                       lastTemperatures[measureIndex]-
                                       lastTemperatures[modIdx(measureIndex-2,lastTemperatureTabSize)];
     DDlastTemperatures[measureIndex]=
                                       DlastTemperatures[measureIndex]-
                                       DlastTemperatures[modIdx(measureIndex-1,lastTemperatureTabSize)];
  
     measureIndex=modIdx(measureIndex+1,lastTemperatureTabSize);
          
     if (      maxDiffTempHistory(relevantBackMinutes)<0.2 
           &&  (zielTemperatur-calcTemp)>0.7 
           &&  lastCorrection<=0 
           &&  stillCorrectionPossible
           
         ) {
       float stablePSLower = stablePSvArr(calcTemp-10);
       float stablePSUpper = stablePSvArr(zielTemperatur+10);
       float incSlope=(stablePSUpper-stablePSLower)/(zielTemperatur+10-(calcTemp-10));
       float targetValPs=onPercentageAvg+(incSlope*(zielTemperatur-calcTemp));
       psCorrFactor=psCorrFactor*psCorrFactor*targetValPs/stablePSvArr(calcTemp);
       showln("psCorrFactor adapted to",psCorrFactor);
       lastCorrection=(relevantBackMinutes*60)/secsBetweenMeasurements;
     } 
     lastCorrection--;
     
     showDisplay();     
     int zielTemperaturInt=zielTemperatur;
     
            
     
     boolean initialiSierung;
               
     if       (calcTemp<=(zielTemperatur-5.0))    {onPercentage=100;                        }
     else {if (calcTemp<=(zielTemperatur-4.0))    {onPercentage=stablePSvArr(zielTemperatur)+30;}
     else {if (calcTemp<=(zielTemperatur-3.0))    {onPercentage=stablePSvArr(zielTemperatur)+20;}
     else {if (calcTemp<=(zielTemperatur-2.0))    {onPercentage=stablePSvArr(zielTemperatur)+10;}
     else {if (calcTemp<=(zielTemperatur-1.0))    {onPercentage=stablePSvArr(zielTemperatur)+5; }
     else {if (calcTemp<=(zielTemperatur-0.2))    {onPercentage=stablePSvArr(zielTemperatur);   }
     }}}}}
     
     if (calcTemp>(zielTemperatur-0.2))   {onPercentage=0;stillCorrectionPossible=false;}   
     if (onPercentageAvg<0 && onPercentage!=0) { onPercentageAvg=onPercentage;} 
     else {         
       onPercentageAvg=((50*onPercentageAvg)+onPercentage)/51;
     }
               
     scheduleOn=isInSchedule();
     
     //45.65C
     if (calibOnPercentage>-1) {
        onPercentage=calibOnPercentage;
     }
     //onPercentage=10;
     
     //59.12<Temp<=59.99
     //onPercentage=20;
     
     //71.5<Temp<72.5
     //onPercentage=30;
     
     //82.2<Temp<82.3
     //onPercentage=40;
     
     clockGetDateTime(); 
     lastTempTakenAbsSecs=clockAbsoluteSecs;
     if (showTempRegularly) {       
       show("actTemp=",calcTemp); 
       show(" zielTemp=",zielTemperatur);
       show(" onpercentage=",onPercentage);
       show(" onPercentageAvg=",onPercentageAvg);
       showTimeShortln(" time=",clock);
     }
              
  }  
  
             
}

int modIdx(int idx, int modulus) {
  while (idx<0) {
    idx=idx+modulus;
  }
  return (idx)%modulus;
}

void clockGetDateTime() {  
  clock.getDate();     
  clockAbsoluteSecs=absouteSecs(clock);
}

int intFromBytes(byte highbyte, byte lowbyte) {
  int highPart=highbyte;
      highPart=highPart<<8;
  return highPart+lowbyte;
}

byte intHighByte(int int2convert) {
  return (byte) (int2convert>>8);
}

byte intLowByte(int int2convert) {
  return (byte) int2convert;
}

float maxDiffTempHistory(int backMinutes) {
    float tempMin=200.0;
    float tempMax=-200.0;
    
    int backSeconds=backMinutes*60; 
    int back2Idx=-(backSeconds/secsBetweenMeasurements);
    
    for (int histIdx=0;histIdx>back2Idx;histIdx--) {
      int currBackIdx=modIdx(histIdx,lastTemperatureTabSize);
      if (lastTemperatures[currBackIdx]<tempMin) {tempMin=lastTemperatures[currBackIdx];}
      if (lastTemperatures[currBackIdx]>tempMax) {tempMax=lastTemperatures[currBackIdx];}      
    }
    return tempMax-tempMin;
}

void showDisplay() {
  
     switch(displayIndicator) {
       case DISPLAY_TEMP_IND:{print2lcd("aktuell  Ziel", calcTemp,zielTemperatur, 10, " C");} break;
       case DISPLAY_TIME_IND:printDate2lcd("Zeit", clock); break;
     } 
  
}

void handleButtonTrigger() {
  
  menuPinVal = pinValTrigger2UP(menuPinVal&1, MENU_SWITCH_PIN);
  upPinVal   = pinValTrigger2UP(upPinVal&1,UP_PIN);
  downPinVal = pinValTrigger2UP(downPinVal&1, DOWN_PIN);
  
  if (menuPinVal==1) {  if (newCalibrateValue && displayIndicator==DISPLAY_CALIB_IND) {
                           for (int eepIdx=calibrateTabOffset;eepIdx<calibrateTabSize+calibrateTabOffset;eepIdx++) {
                                EEPROM.write(eepIdx,calibrateTabBt[eepIdx]);
                            }
                            newCalibrateValue=false;
                        }
                        displayIndicator=(displayIndicator+1)%DISPLAY_ANZ;
                        showDisplay();
                     }
                     
  if (upPinVal==1) {//showln("upButton pressed ", 0);
                     switch(displayIndicator) {
                       case DISPLAY_TEMP_IND:{zielTemperatur=zielTemperatur+0.5;} break;
                       case DISPLAY_TIME_IND:{printDate2lcd("Zeit", clock);} break;                       
                     }
                     pressedButtonTime=millis();
                     showDisplay();
                    }
  if (downPinVal==1) {//showln("downButton pressed ", 0);
                      switch(displayIndicator) {
                       case DISPLAY_TEMP_IND:{zielTemperatur=zielTemperatur-0.5;;} break;
                       case DISPLAY_TIME_IND:{}; break;                       
                       }
                       pressedButtonTime=millis();
                       showDisplay();
                      }
         
             
  if (upPinVal==3 && (millis()-pressedButtonTime)>500) {
                          switch(displayIndicator) {
                           case DISPLAY_TEMP_IND:{zielTemperatur=zielTemperatur+0.5;} break;
                           case DISPLAY_TIME_IND:{printDate2lcd("Zeit", clock);} break;                          
                          }
                          showDisplay();
  }
  
  if (downPinVal==3 && (millis()-pressedButtonTime)>500) {
                          switch(displayIndicator) {
                           case DISPLAY_TEMP_IND:{zielTemperatur=zielTemperatur-0.5;} break;
                           case DISPLAY_TIME_IND:{printDate2lcd("Zeit", clock);} break;
                          }
                          showDisplay();
  }
  
  if (menuPinVal==0 && upPinVal==0 && downPinVal==0) {pressedButtonTime=0;}   
  
      
}



int pinValTrigger2UP(int currVal, int PIN) {
   int pinVal=digitalRead(PIN);
   int retVal=(currVal<<1)+pinVal;   
   return retVal;
}

void printDate2lcd(String title, DS1107H timerChip) {
   
  lcd.setCursor(0, 0);
  lcd.print("                ");
  lcd.setCursor(0, 0);
  lcd.print(title);
  lcd.setCursor(0, 1);
  lcd.print("                ");
  lcd.setCursor(0, 1);
  
  lcd.print (timerChip.hour);
  lcd.print (":");
  lcd.print (timerChip.minute);
  lcd.print ("h");
  
}

void print2lcd(String title, float value1, float value2, int precision, String finalString) {
  
  lcd.setCursor(0, 0);
  lcd.print("                ");
  lcd.setCursor(0, 0);
  lcd.print(title);
  lcd.setCursor(0, 1);
  lcd.print("                     ");
  lcd.setCursor(0, 1);
  lcd.print("  ");
  printFloat2lcd(value1,precision);
  lcd.print("  ");
  printFloat2lcd(value2,precision);
    
  lcd.print(finalString);
  
}

void printFloat2lcd(float value,int precision) {
  
  if (value<0) {
    lcd.print("-");
  }
  lcd.print (int(value));  //prints the int part
  lcd.print(".");          // print the decimal point
  unsigned int frac;
  if(value >= 0)
        frac = (value - int(value)) * precision;
  else
        frac = (int(value)- value ) * precision;
  
  lcd.print(frac);
  
}





float stablePsx(float targetTemp) {
  
  float retVal;
  if (targetTemp<40.85)                        {retVal=P_s(targetTemp,  0.0 ,10.0, 23.00,  40.85);}
  if (40.85<=targetTemp  && targetTemp<=55.05) {retVal=P_s(targetTemp, 10.0 ,20.0, 40.85,  55.05);}
  if (55.05<=targetTemp  && targetTemp<=68.25) {retVal=P_s(targetTemp, 20.0 ,30.0, 55.05,  68.25);}
  if (68.25<=targetTemp  && targetTemp<=77.00) {retVal=P_s(targetTemp, 30.0 ,40.0, 68.25,  77.00);} 
  if (77.00<=targetTemp  && targetTemp<=85.00) {retVal=P_s(targetTemp, 40.0 ,50.0, 77.00,  85.00);}   
  if (85.00<targetTemp                      )  {retVal=P_s(targetTemp, 50.0 ,60.0, 85.00,  100.00);} 
  
  return retVal;
}

void initStablePSArr() {  
      stablePSArr[0]=23.00;
      stablePSArr[1]=40.85;
      stablePSArr[2]=55.05;
      stablePSArr[3]=68.24;
      stablePSArr[4]=77.00;
      stablePSArr[5]=89.00;
      stablePSArr[6]=95.00;
      stablePSArr[7]=97.00;
      stablePSArr[8]=98.00;
      stablePSArr[9]=99.00;
}

float stablePSvArr(float targetTemp) {
  float retVal=-1;
  for (int i=0;i<stablePSArrSize-1;i++) {
    if (stablePSArr[i]<=targetTemp && targetTemp<=stablePSArr[i+1]) {
      retVal=P_s(targetTemp,  i*10.0 ,(i+1)*10.0, stablePSArr[i],  stablePSArr[i+1]);
    }
  }
  return retVal*psCorrFactor;
}


float t_k(float a, float b, float T1, float T2) {
  return (b-a)/(T2-T1);
}

float t_d(float a, float b, float T1, float T2) {
  return (a*T2-b*T1)/(T2-T1);
}

float P_s(float T_s, float a, float b, float T1, float T2) {
  return t_k(a, b, T1, T2)*T_s+t_d(a, b, T1, T2);
}


boolean isInSchedule() {
  boolean retVal=false;
  clock.getDate();
  if (clock.hour>=6 && clock.hour<9) {retVal=true;}
  //if (clock.hour>=23) {retVal=true;}
  retVal=true;
  return retVal;
}


boolean isOn(long onPercentage) {
  unsigned long time = millis();  
  //long partOfMinute= (time/1000)%(60);
  //return ((5*partOfMinute/3)<=onPercentage);  
  long partOf10Sec= (time%10000)/100;  
  return (partOf10Sec<onPercentage);
}

void setEinschaltVerhaeltnis(int prozentsatz) {
  onPercentage=prozentsatz;
}


void putSwitch(boolean valueOn) {
     switchOn=valueOn;     
   if (switchOn) {
     digitalWrite(SWITCH_PIN, HIGH);
   } else {
     digitalWrite(SWITCH_PIN, LOW);
   }
}

void showln(char nameStr[], double val) {
  Serial.print(nameStr);    
  Serial.println(val);
  
}

void show(char nameStr[], double val) {
  Serial.print(nameStr);
  Serial.print(val);
}

void showTimeShortln(char nameStr[], DS1107H timerChip) {
  showTimeShort(nameStr,timerChip);
  Serial.println(" ");
}

void showTimeShort(char nameStr[], DS1107H timerChip) {  
  Serial.print(nameStr);
  Serial.print(timerChip.hour, DEC);
  Serial.print(":");
  Serial.print(timerChip.minute, DEC);
  Serial.print(":");
  Serial.print(timerChip.second, DEC);
}

void showTime(DS1107H timerChip) {
  Serial.print("   UT ");
  Serial.print(timerChip.hour, DEC);
  Serial.print(":");
  Serial.print(timerChip.minute, DEC);
  Serial.print(":");
  Serial.print(timerChip.second, DEC);
  Serial.print("  ");
  Serial.print(timerChip.dayOfMonth, DEC);
   Serial.print("/");
  Serial.print(timerChip.month, DEC); 
  Serial.print("/");
  Serial.print(timerChip.year, DEC);
  Serial.print("  Day_of_week:");
  Serial.println(timerChip.dayOfWeek, DEC);  
}

//only approximate Secs, covering within one month, good enough for diffbuilding
long absouteSecs(DS1107H tc) {
  unsigned long minuteSecs=60;
  unsigned long hourSecs=minuteSecs*60;
  unsigned long daySecs=hourSecs*24;
  unsigned long monthSecs=daySecs*30;
  unsigned long yearSecs=monthSecs*12;
  return tc.second
  +(tc.minute*minuteSecs)
  +(tc.hour*hourSecs)
  +(tc.dayOfMonth*monthSecs)
  ; 
}

int getCalibrateIdx(float potential) {
    return int (potential/50);
}





